package world.snowcrystal.service.Impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.RandomStringUtils;
import org.redisson.api.RMapCache;
import org.redisson.api.RSetCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import world.snowcrystal.dao.UserAuthenticationDetails;
import world.snowcrystal.dto.RegistrationRequestMessage;
import world.snowcrystal.exception.*;
import world.snowcrystal.mapper.UserAuthenticationDetailsMapper;
import world.snowcrystal.properties.AuthenticationProperties;
import world.snowcrystal.service.UserAuthenticationService;

import java.sql.SQLException;
import java.util.concurrent.TimeUnit;


/**
 * 提供 UserDetailsService 以及 Remember-Me 服务
 *
 * @author Tianqing
 * @since 1.0
 */


@Service("userAuthenticationService")
@Log4j2
public class DefaultUserAuthenticationService
        implements UserAuthenticationService {


    private final UserAuthenticationDetailsMapper mapper;

    private final PasswordEncoder encoder;

    @Autowired
    @Qualifier("inPlaceTokenList")
    private RSetCache<String> inPlaceTokenBucket;

    @Autowired
    @Qualifier("mailVerificationQueue")
    private RMapCache<String, RegistrationRequestMessage> registrationRequestCache;

    @Autowired
    private AuthenticationProperties authenticationProperties;

    public DefaultUserAuthenticationService(PasswordEncoder encoder,
                                            UserAuthenticationDetailsMapper mapper) {
        this.encoder = encoder;
        this.mapper = mapper;
    }


    @Override
    public int addNewUserKG(UserAuthenticationDetails userAuthenticationDetails)
            throws SQLException {
        String raw = userAuthenticationDetails.getPassword();
        encodePassword(userAuthenticationDetails);
        int result =mapper.insertNewUserKG(userAuthenticationDetails);
        userAuthenticationDetails.setPassword(raw);
        return result;
    }

    private void encodePassword(UserAuthenticationDetails raw) {
        raw.setPassword(encoder.encode(raw.getPassword()));
    }

    /**
     * 用于更新用户的认证信息
     *
     * @param userAuthenticationDetails 如果提供的字段不为 null ，那么字段将被更新到数据库中
     * @author Tianqing
     * @since 1.0
     */
    @Override
    @Transactional
    public void updateUserAuthenticationDetails(UserAuthenticationDetails userAuthenticationDetails) {
        throw new NotImplementedException();
    }

    /**
     * 用于修改密码，需要提供旧的密码
     *
     * @param oldPassword 旧密码
     * @param newPassword 新密码
     * @author Tianqing
     * @since 1.0
     */
    @Override
    @Transactional
    public void updateUserPassword(String username, @Nullable String oldPassword, String newPassword)
            throws SnowcrystalUserNotFoundException, SnowcrystalPasswordMismatchException, SnowcrystalOperationFailedException {
        UserAuthenticationDetails userAuthenticationDetails = mapper.selectOne(new QueryWrapper<UserAuthenticationDetails>()
                .checkSqlInjection()
                .select("id","password")
                .eq("username", username), false);
        int result = 0;
        if (userAuthenticationDetails == null) {
            throw new SnowcrystalUserNotFoundException("user not found");
        }
        if (oldPassword != null) {
            if (!encoder.matches(oldPassword, userAuthenticationDetails.getPassword())) {
                throw new SnowcrystalPasswordMismatchException("password mismatch");
            }
        }
        result = mapper.update(new UpdateWrapper<UserAuthenticationDetails>().set("password",
                        encoder.encode(newPassword))
                .eq("id", userAuthenticationDetails.getId()));
        if(result != 1){
            throw new SnowcrystalOperationFailedException("fail to update");
        }
    }

    @Override
    @Transactional
    public void updateUserPassword(Long id, @Nullable String oldPassword, String newPassword)
            throws SnowcrystalUserNotFoundException, SnowcrystalPasswordMismatchException, SnowcrystalOperationFailedException {

        UserAuthenticationDetails userAuthenticationDetails = mapper.selectOne(new QueryWrapper<UserAuthenticationDetails>()
                .checkSqlInjection()
                .select("password")
                .eq("id", id), false);
        int result = 0;
        if (userAuthenticationDetails == null) {
            throw new SnowcrystalUserNotFoundException("user not found");
        }
        if (oldPassword != null) {
            if (!encoder.matches(oldPassword, userAuthenticationDetails.getPassword())) {
                throw new SnowcrystalPasswordMismatchException("password mismatch");
            }
        }
        result = mapper.update(new UpdateWrapper<UserAuthenticationDetails>().set("password",
                        encoder.encode(newPassword))
                .eq("id", id));
        if(result != 1){
            throw new SnowcrystalOperationFailedException("update password failed");
        }
    }


    /**
     * 授予用户权限
     *
     * @param authorities 以字符串表示的权限信息，详见 {@link UserAuthenticationDetails}
     * @author Tianqing
     * @since 1.0
     */
    @Override
    @Transactional
    public void grantUserAuthorities(String... authorities) {
        throw new NotImplementedException();
    }

    /**
     * 收回用户权限信息，如果用户没有某个权限，则对于这个权限什么都不做
     *
     * @param authorities 将要被收回的权限
     * @author Tianqing
     * @see #grantUserAuthorities(String...)
     * @since 1.0
     */
    @Override
    @Transactional
    public void revokeUserAuthorities(String... authorities) {
        throw new NotImplementedException();
    }

    @Override
    public boolean authenticateUser(String username, String password) {
        UserAuthenticationDetails userAuthenticationDetails = mapper.selectOne(new QueryWrapper<UserAuthenticationDetails>()
                .checkSqlInjection()
                .select("password").eq("username", username), false
        );
        if (userAuthenticationDetails == null) {
            throw new SnowcrystalUserNotFoundException("user not found");
        }
        return encoder.matches(password, userAuthenticationDetails.getPassword());
    }


    @Override
    public void putRegistrationRequest(RegistrationRequestMessage msg) {
        registrationRequestCache.put(msg.getMail(),
                msg,
                authenticationProperties.getMailVerificationExpirationTime(),
                TimeUnit.SECONDS);
    }

    @Override
    public boolean checkValidationCode(String mail, String verificationCode)
            throws SnowcrystalValidationCodeExpireException, SnowcrystalValidationCodeMismatchException {
        RegistrationRequestMessage registrationRequestMessage = registrationRequestCache.get(mail);
        if (registrationRequestMessage == null) {
            throw new SnowcrystalValidationCodeExpireException("过期了哦");
        }
        if (verificationCode.equals(registrationRequestMessage.getVerificationCode())) {
            return true;
        } else {
            throw new SnowcrystalValidationCodeMismatchException("验证码不对哦");
        }
    }

    /**
     * 生成具有固定长度的认证码
     *
     * @author tianqing
     * @since 1.0
     */
    @Override
    public String generateVerificationCode() {
        return RandomStringUtils.randomAlphabetic(authenticationProperties.getVerificationCodeLength());
    }

    /**
     * 生成 in-place 操作的 token，要求具有一定安全性
     * 以 12-16 个字符为宜，并将其存储到队列中暂时保存
     *
     * @author tianqing
     * @since 1.0
     */
    @Override
    public String generateInPlaceOperationToken() {
        return RandomStringUtils.randomAlphanumeric(16);
    }

    /**
     * 检查 in-place 操作 token 是否已经过期，如果已经过期，则返回 false
     *
     * @author tianqing
     * @since 1.0
     */
    @Override
    public boolean checkInPlaceOperationTokenAlive(@Nonnull String inPlaceOperationToken) {
        return inPlaceTokenBucket.contains(inPlaceOperationToken);
    }

    /**
     * 生成并设置 in-place token；
     * <li> 生成 token</li>
     * <li> 将其保存到队列</li>
     * <li> 设置 cookie</li>
     * @author tianqing
     * @since 1.0
     */
    @Override
    public String setInPlaceOperationToken(HttpServletRequest request, HttpServletResponse response) {
        String token = generateInPlaceOperationToken();
        int expirationTime = authenticationProperties.getInPlaceOperationTokenExpirationTime();
        inPlaceTokenBucket.add(token,expirationTime,TimeUnit.SECONDS);
        ResponseCookie cookie = ResponseCookie.from(authenticationProperties.getInPlaceOperationTokenCookieName(), token)
                .path(authenticationProperties.getCookiePath())
                .httpOnly(true)
                .domain(authenticationProperties.getCookieDomain())
                .sameSite("None")
                .secure(authenticationProperties.getCookieSecure())
                .maxAge(expirationTime)
                .build();

        if(response.isCommitted()){
            log.error("The response has been committed.");
        }
        response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
        return token;
    }


}
